package org.example.backend.aspect;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.example.backend.entity.OperationLog;
import org.example.backend.service.OperationLogService;
import org.example.backend.util.IpUtil;
import org.example.backend.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;

/**
 * 日志切面
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private OperationLogService operationLogService;

    @Autowired
    private JwtUtil jwtUtil;

    /**
     * 定义切点 - 所有controller包下的方法
     */
    @Pointcut("execution(public * org.example.backend.controller.*.*(..))")
    public void controllerLog() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint 切点
     */
    @Before("controllerLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();
    }

    /**
     * 后置返回通知
     *
     * @param joinPoint 切点
     * @param result    返回结果
     */
    @AfterReturning(pointcut = "controllerLog()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        // 记录操作日志
        saveOperationLog(joinPoint, result, null);
        // 避免序列化复杂对象导致的问题
        try {
            // 检查result是否可序列化
            if (result != null && !(result instanceof javax.servlet.http.HttpServletResponse) &&
                !result.getClass().getName().contains("org.apache.catalina")) {
                log.info("RESPONSE: {}", JSON.toJSONString(result));
            } else {
                log.info("RESPONSE: [无法序列化的响应对象]");
            }
        } catch (Exception e) {
            log.info("RESPONSE: [序列化响应失败: {}]", e.getMessage());
        }
    }

    /**
     * 异常通知
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(pointcut = "controllerLog()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        // 记录操作日志
        saveOperationLog(joinPoint, null, e.getMessage());
        log.error("EXCEPTION: {}", e.getMessage());
    }

    /**
     * 保存操作日志
     *
     * @param joinPoint 切点
     * @param result    返回结果
     * @param errorMsg  错误信息
     */
    private void saveOperationLog(JoinPoint joinPoint, Object result, String errorMsg) {
        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest request = attributes.getRequest();

        // 获取用户信息
        String token = request.getHeader("Authorization");
        Long userId = null;
        String username = null;
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
            try {
                userId = jwtUtil.getUserIdFromToken(token);
                username = jwtUtil.getUsernameFromToken(token);
            } catch (Exception e) {
                log.error("获取用户信息失败", e);
            }
        }

        // 创建操作日志
        OperationLog operationLog = new OperationLog();
        operationLog.setUserId(userId);
        operationLog.setUsername(username);
        operationLog.setOperation(joinPoint.getSignature().getName());
        operationLog.setMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

        // 修改参数序列化方式，过滤掉不可序列化的对象类型
        Object[] args = joinPoint.getArgs();
        if (args != null && args.length > 0) {
            try {
                // 针对文件上传场景，将MultipartFile替换为描述信息
                Object[] safeArgs = new Object[args.length];
                for (int i = 0; i < args.length; i++) {
                    if (args[i] == null) {
                        safeArgs[i] = null;
                    } else if (args[i] instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) args[i];
                        safeArgs[i] = String.format("[文件名:%s, 大小:%d字节, 类型:%s]",
                            file.getOriginalFilename(),
                            file.getSize(),
                            file.getContentType());
                    } else if (args[i] instanceof HttpServletRequest) {
                        safeArgs[i] = "[HttpServletRequest]";
                    } else if (args[i] instanceof javax.servlet.http.HttpServletResponse) {
                        safeArgs[i] = "[HttpServletResponse]";
                    } else if (args[i].getClass().getName().contains("org.apache.catalina")) {
                        safeArgs[i] = "[ServletContainer对象]";
                    } else {
                        safeArgs[i] = args[i];
                    }
                }
                operationLog.setParams(JSON.toJSONString(safeArgs));
            } catch (Exception e) {
                log.error("参数序列化失败", e);
                operationLog.setParams("[序列化失败的参数]");
            }
        } else {
            operationLog.setParams("[]");
        }

        operationLog.setIp(IpUtil.getIpAddr(request));
        operationLog.setCreateTime(new Date());

        // 设置操作状态
        if (errorMsg != null) {
            operationLog.setStatus(0); // 失败
            operationLog.setErrorMsg(errorMsg);
        } else {
            operationLog.setStatus(1); // 成功
        }

        // 保存操作日志
        try {
            operationLogService.save(operationLog);
        } catch (Exception e) {
            log.error("保存操作日志失败", e);
        }
    }
}
